package com.example.juc.big_talk_concurrent.chapter2.article9;

public class FinalTest {
    private int a;  // 普通域
    private final int b; // final 域
    private static FinalTest finalTest;

    public FinalTest() {
        a = 1; // 1. 写普通域
        b = 2; // 2. 写 final 域
    }

    // 线程 A 执行
    public static void writer() {
        finalTest = new FinalTest();
    }

    // 线程 B 执行
    public static void reader() {
        FinalTest object = finalTest; // 3. 读对象引用
        int a = object.a;    // 4. 读普通域
        int b = object.b;    // 5. 读 final 域
    }
}

// 如上，writer 方法只有一行代码，但实际上包含两个大步骤：
//
//构造 FinalTest 对象
//写普通域：a = 1
//写 final 域：b = 2
//把这个对象的引用赋值给引用变量 finalTest
//JMM 禁止编译器把对 final 域的写指令重排序到构造函数之外，但对于写普通域并没有类似的要求，所以 writer 方法可能的会出现下面这样的执行顺序：
//
//构造 FinalTest 对象
//写 final 域：b = 2
//把这个对象的引用赋值给引用变量 finalTest
//写普通域：a = 1
// 读线程 B 看到对象引用的时候，对象还没有构造完成，因为写普通域的操作被编译器重排序到了构造函数之外，此时初始值 1 还没有被写入普通域 a。
//
//所以，读线程 B 错误地读取到了普通变量 a 初始化之前的值。而写 final 域的操作，由于写 final 域的重排序规则的存在，被限定在了构造函数之内，所以读线程 B 正确地读取到了 final 变量初始化之后的值。


// 仍然看上一小节的例子，reader 方法包含三个步骤：
//
//
//st=>start: Start
//op1=>operation: 读对象引用
//op2=>operation: 读对象引用的普通域
//op3=>operation: 读对象引用的 final 域
//e=>end
//
//st->op1->op2->op3->e
//同样的，JMM 禁止处理器对这两个操作进行重排序，但对于读普通域并没有类似的要求，所以如果在不遵守间接依赖关系的处理器上，reader 方法可能的会出现这样的执行顺序：
//
//
//st=>start: Start
//op1=>operation: 读对象引用
//op2=>operation: 读对象引用的普通域
//op3=>operation: 读对象引用的 final 域
//e=>end
//
//st->op2->op1->op3->e
//
//在上图中，读对象的普通域的操作被处理器重排序到读对象引用之前。线程 B 读普通域时，该域还没有被线程 A 写入。而读 final 域的重排序规则会把读对象 final 域的操作限定在读对象引用之后，此时该 final 域已经被线程 A 初始化过了，所以能够正确的读取到。

